package com.autonavi.yunda.yunji.core.engine;

import com.autonavi.yunda.yunji.common.exception.AmapException;
import com.autonavi.yunda.yunji.common.exception.AmapExceptionCode;
import com.autonavi.yunda.yunji.core.engine.argresolver.ParamResolver;
import com.autonavi.yunda.yunji.core.engine.context.NodeContext;
import com.autonavi.yunda.yunji.core.engine.exception.EngineException;
import com.autonavi.yunda.yunji.core.engine.script.IFunctionScript;
import com.autonavi.yunda.yunji.core.enums.AccessStatus;
import com.autonavi.yunda.yunji.core.enums.NodeStatus;
import com.autonavi.yunda.yunji.core.enums.NodeType;
import groovy.lang.GString;
import groovy.lang.GroovyObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.autonavi.yunda.yunji.core.config.EngineConstant.*;

/**
 * @author cizhong.wcs
 * @date 2023/8/22 11:38
 */
@Slf4j
public abstract class AbstractEngineWorker {
    protected static final Logger engineLogger = LoggerFactory.getLogger("logger.engine.exec.log");

    /**
     * 验证当前节点状态和上游节点状态
     * 并执行当前节点
     *
     * @param context    当前节点
     * @param accessible 上个条件指定当前节点状态
     */
    protected void checkAndExecNode(NodeContext context, AccessStatus accessible) {

        if (context.inaccessibleCheck(accessible)) {
            context.setStatus(NodeStatus.INACCESSIBLE);
        }
        // 如果是特殊节点直接置为成功
        else if (context.getNode().getNodeType().isSpecial()) {
            context.setStatus(NodeStatus.SUCCESS);
        } else {
            try {
                //执行当前节点
                execNode(context);
                context.setStatus(NodeStatus.SUCCESS);
            } catch (Throwable e) {
                if (e instanceof EngineException) {
                    log.warn("node {} execute error", context.getName(), e);
                } else {
                    log.error("node {} execute error", context.getName(), e);
                }
                context.setStatus(NodeStatus.FAIL);
                context.setErrorMessage(String.format("[%s节点]执行发生异常: %s", context.getName(), e.getMessage()));
                context.setException(e);
            }
        }
    }

    /**
     * 判断条件获取下游执行节点列表
     */
    protected List<Pair<NodeContext, AccessStatus>> fetchNextNodes(NodeContext context) {
        List<Pair<NodeContext, AccessStatus>> nextNodes = new ArrayList<>();
        // 如果失败，返回子节点集合？？？
        if (context.failCheck()) {
            return nextNodes;
        }
        // 若当前节点条件不可达 ？？？
        if (context.getStatus().isInaccessible()) {
            context.getNextNodeContext().forEach(nextContext -> {
                if (nextContext.executeCheckAndSetRunning()) {
                    nextNodes.add(Pair.of(nextContext, AccessStatus.INACCESSIBLE));
                } else {
                    engineLogger.info("node {} executeCheckAndSetRunning false,exe ignore", nextContext.getName());
                }
                context.addNextNodeAccessStatus(nextContext.getName(), AccessStatus.INACCESSIBLE);
            });
            return nextNodes;
        }
        List<NodeContext> defaultNextNodes = context.getDefaultNextNodeContext();
        //是否执行default条件的节点
        boolean triggerDefaultNextNode = true;
        for (NodeContext nextContext : context.getNextNodeContext()) {
            //若并行执行 条件执行时检查 防止执行多余条件
            if (context.failCheck()) {
                break;
            }
            if (defaultNextNodes.contains(nextContext)) {
                continue;
            }
            // 校验连接线条件，判断那个分支是可达的
            String switcherFunctionName = context.getNodeSwitcherFunctionName(nextContext.getName());
            AccessStatus access = AccessStatus.ACCESSIBLE;
            if (switcherFunctionName != null) {
                try {
                    access = AccessStatus.fromBoolean(execEdge(context, switcherFunctionName));
                    if (AccessStatus.ACCESSIBLE.equals(access)) {
                        triggerDefaultNextNode = false;
                    }
                } catch (Throwable e) {
                    context.setStatus(NodeStatus.CONDITION_FAIL);
                    log.error("node access next execute error, switcherFunctionName = {}", switcherFunctionName);
                    context.setErrorMessage(String.format("[%s节点]到[%s节点]条件执行发生异常: %s", context.getName(), nextContext.getName(), e.getMessage()));
                    context.setException(e);
                }
            }
            engineLogger.info("nextContext {} access {}", nextContext.getName(), access);
            //检测下游节点是否可执行,若可执行,则设置为执行状态,并放到执行列表中
            if (nextContext.executeCheckAndSetRunning()) {
                nextNodes.add(Pair.of(nextContext, access));
            } else {
                engineLogger.info("node {} executeCheckAndSetRunning false,exe ignore", nextContext.getName());
            }
            context.addNextNodeAccessStatus(nextContext.getName(), access);
        }
        AccessStatus defaultNextNodeAccess = triggerDefaultNextNode ? AccessStatus.ACCESSIBLE : AccessStatus.INACCESSIBLE;
        defaultNextNodes.forEach(defaultNextNode -> {
            if (context.failCheck()) {
                return;
            }
            engineLogger.info("defaultNextNode {} access {}", defaultNextNode.getName(), defaultNextNodeAccess);
            if (defaultNextNode.executeCheckAndSetRunning()) {
                nextNodes.add(Pair.of(defaultNextNode, defaultNextNodeAccess));
            } else {
                engineLogger.info("node {} executeCheckAndSetRunning false,exe ignore", defaultNextNode.getName());
            }
            context.addNextNodeAccessStatus(defaultNextNode.getName(), defaultNextNodeAccess);
        });
        return nextNodes;
    }

    /**
     * 具体的执行节点逻辑
     */
    private void execNode(NodeContext nodeContext) {
        Map<String, Object> paramMapping = (Map<String, Object>) nodeContext.getEngineContext().invoke(nodeContext.getName());
        String varName = nodeContext.getNode().getVar();
        IFunctionScript functionScriptClass = nodeContext.getEngineContext().scriptClass.getFunctionScriptClass(nodeContext.getName());
        //参数类型转换调整
        paramMapping.forEach((paramName, paramValue) -> {
            String paramType = nodeContext.getNode().getParamNameTypeMap().get(paramName);
            paramMapping.put(paramName, ParamResolver.getParamValueByType(paramValue, paramType));
        });
        //transform 节点参数特殊操作
        if (nodeContext.getNode().getNodeType().equals(NodeType.transform)) {
            paramMapping.put(SCRIPT_PARAMS_MAP_NAME, new HashMap<>(paramMapping));
        }
        //执行节点class process
        Object result = functionScriptClass.exeFun(paramMapping, EXEC_PROCESS);
        if (result instanceof GString) {
            result = result.toString();
        }
        if (nodeContext.getEngineContext().debug) {
            nodeContext.setNodeOutput(result);
        }
        if (!nodeContext.getNode().getResultValidators().isEmpty()) {
            functionScriptClass.exeFun(null, EXEC_VALIDATOR_RESULT_PROCESS, result);
        }
        GroovyObject owner = (GroovyObject) nodeContext.getEngineContext().scriptInstance;
        owner.setProperty(varName, result);
    }

    /**
     * 执行连接线条件
     * @param nodeContext
     * @param switcherFunctionName
     * @return
     */
    private Boolean execEdge(NodeContext nodeContext, String switcherFunctionName) {
        // 执行脚本对象方法，获取需要哪些参数
        Map<String, Object> paramMapping = (Map<String, Object>) nodeContext.getEngineContext().invoke(switcherFunctionName);
        // 参数类型转换调整
        paramMapping.forEach((paramName, paramValue) -> {
            String paramType = nodeContext.getNodeSwitcherParamNameType(switcherFunctionName, paramName);
            paramMapping.put(paramName, ParamResolver.getParamValueByType(paramValue, paramType));
        });
        IFunctionScript functionScriptClass = nodeContext.getEngineContext().scriptClass.getFunctionScriptClass(switcherFunctionName);
        //执行脚本
        Object result = functionScriptClass.exeFun(paramMapping, EXEC_PROCESS);
        if (result instanceof Boolean) {
            return (Boolean) result;
        } else {
            throw new AmapException(AmapExceptionCode.COMMON_ILLEGAL_PARAM, switcherFunctionName + "条件脚本回必须为true或者false");
        }
    }
}
